package net;

import java.util.LinkedList;


public class KohonenMaps extends UnsupervisedNet {

	protected double learningRate,adjusteFactor,minLearningRate;
	protected int iterations,radius,reductionMode;
	protected boolean decrementRadius; 

	public static final int LINEAR_REDUCTION = 1;
	public static final int GEOMETRIC_REDUCTION = 1;

	public KohonenMaps(int neuroniosEntrada, int neuroniosSaida) {
		super(neuroniosEntrada, neuroniosSaida);
		decrementRadius = true;
		for(int i=0;i<neuroniosSaida;i++){
			addClasse();
		}
	}

	public void addClasse(){
		classes.add(new Class(null,"Class:" +(classes.size()+1)));
	}

	public void addSample(String file,String description){
		super.addSample(file,null,description);
	}

	@Override
	public void classify() {
		LinkedList<Double> distances;
		for(int i=0;i<=iterations ;i++){		
			for(Sample s : samples){
				distances = new  LinkedList<Double>();
				setNeuronInputs(s.getInput());
				for(Neuron n : outputLayer){
					distances.add(euclidianDistance(s, n));
				}	
				Neuron winner  = closestNeuron(distances);
				s.setClasse(classFromNeuron(winner));
				updateWeights(winner);			
			}
			if (reductionMode == GEOMETRIC_REDUCTION) { 
				learningRate*= adjusteFactor;
			}else{
				if(learningRate > minLearningRate ){
					learningRate -= adjusteFactor;
				}
			}
			if ((decrementRadius) &&  (radius > 0)){
				radius--;
			}
		}
		for (Class c:classes){
			LinkedList<Sample> elements = getElements(c);
			System.out.println(c);
			for(Sample s:elements){				
				System.out.println(s);
			}
		}
	}
	
	public double euclidianDistance(Sample s, Neuron n){
		double distance =0;
		for(int i=0;i< n.getInputList().size();i++){
			Synapse syn = n.getInputList().get(i);
			distance += Math.pow(syn.getWeight().doubleValue() - s.getInput()[i].doubleValue(), 2);
		}
		return distance;
	}

	public Class classFromNeuron(Neuron n){
		int index = outputLayer.indexOf(n);
		if (index > -1){
			return classes.get(index);
		}
		return null;
	}
	
	public LinkedList<Sample> getElements(Class c){
		LinkedList<Sample> result = new LinkedList<Sample>();
		for(Sample s:samples){
			if(s.getClasse() == c){
				result.add(s);
			}
		}
		return result;
	}



	public <E extends Number> Neuron closestNeuron(LinkedList<E> distances){
		int index = 0;
		Number min = Double.MAX_VALUE;
		for(int i=0;i<distances.size();i++){
			Number x = distances.get(i);
			if(x.doubleValue() < min.doubleValue()){
				min = x.doubleValue();
				index = i;
			}
		}
		return outputLayer.get(index);
	}

	public void updateWeights(Neuron n){
		int index;
		updateWeigth(n);
		for(int i=1;i<=radius;i++){
			index = outputLayer.indexOf(n)-i;			
			if(index < 0){
				index = outputLayer.size()+ index;
			}else if (index > outputLayer.size()){
				index = outputLayer.size() - index;
			}
			Neuron neighbor = outputLayer.get(index);
			updateWeigth(neighbor);
		}
	}
	
	public double getLearningRate() {
		return learningRate;
	}

	public void setLearningRate(double learningRate) {
		this.learningRate = learningRate;
	}

	public double getMinLearningRate() {
		return minLearningRate;
	}

	public void setMinLearningRate(double minLearningRate) {
		this.minLearningRate = minLearningRate;
	}

	public int getReductionMode() {
		return reductionMode;
	}

	public void setReductionMode(int reductionMode) {
		this.reductionMode = reductionMode;
	}

	public double getAdjusteFactor() {
		return adjusteFactor;
	}

	public void setAdjusteFactor(double adjusteFactor) {
		this.adjusteFactor = adjusteFactor;
	}

	public int getIterations() {
		return iterations;
	}

	public void setIterations(int iterations) {
		this.iterations = iterations;
	}

	public int getRadius() {
		return radius;
	}

	public void setRadius(int radius) {
		this.radius = radius;
	}

	private void updateWeigth(Neuron n){
		for(Synapse s:n.getInputList()){
			s.setWeight(s.getWeight().doubleValue()+ learningRate*(s.getInput().getOutput().doubleValue() -s.getWeight().doubleValue()));
		}
	}

	public boolean isDecrementRadius() {
		return decrementRadius;
	}

	public void setDecrementRadius(boolean decrementRadius) {
		this.decrementRadius = decrementRadius;
	}
	
}
